import { useGlobSetting } from "@/hooks/setting";
import { useMessage } from "@/hooks/web/use-message";
import { tokenCache } from "@/utils/cache";
import { createFetch, type CreateFetchOptions, type UseFetchReturn } from "@vueuse/core";
import { type Ref } from "vue";

export type ErrorMessageMode = "none" | "modal" | "message" | undefined;
export type ApiResponse<T = any> = { success: boolean; code: number; message?: string; data?: T };
export type SearchParams = string | string[][] | Record<string, string> | URLSearchParams | undefined;

export class ApiFetch {
  /** 后端 Controller Request Mapping */
  protected prefix: string = "";

  public static merge<T extends object>(target: Nullable<T>, ...sources: T[]): T {
    if (sources.length === 0) {
      if (target === null) throw "输入参数均为空，不能合并。";
      return target;
    }
    const source = sources.shift(); // WARNING: sources has been changed.
    const _merge = (t: Nullable<Object>, s: Object) => {
      t = t || {};
      for (const key of Object.keys(s)) {
        let vt = Reflect.get(t, key);
        let vs = Reflect.get(s, key);
        if (vs instanceof Object) _merge(<T>vt, <T>vs);
      }
      Object.assign(t, s);
    };
    if (source) _merge(target, source);
    return ApiFetch.merge(target, ...sources);
  }

  public static clone<T extends object>(source: T): T {
    const _clone = (t: object, s: object) => {
      t = t || {};
      for (const key of Object.keys(s)) {
        let vt = Reflect.get(t, key);
        let vs = Reflect.get(s, key);
        if (vs instanceof Object) _clone(<T>vt, <T>vs);
      }
      Object.assign(t, s);
      return t;
    };
    return <T>_clone({}, source);
  }

  protected prepare<T>(action: string, mode?: ErrorMessageMode, ri?: RequestInit) {
    const checkStatus = this.checkStatus;
    const cfo: CreateFetchOptions = {
      baseUrl: useGlobSetting().apiUrl,
      options: {
        beforeFetch({ url, options, cancel }) {
          const token = tokenCache.get();
          if (token) options.headers = { ...options.headers, Authorization: `Bearer ${token}` };
          return { url, options, cancel };
        },
        afterFetch({ data, response }) {
          const contentType = response.headers.get("Content-Type");
          if (contentType?.startsWith("application/json")) {
            return { data: JSON.parse(data), response };
          } else {
            return { data, response };
          }
        },
        onFetchError({ data, response, error }) {
          let ar: ApiResponse | undefined = undefined;
          if (data) ar = JSON.parse(data);
          if (!ar) {
            ar = { success: false, code: response?.status || 500, message: `服务器不可用，请检查网络或联系管理员。${response?.statusText || "未知原因"}` };
          } else {
            ar.code = response?.status || ar?.code || 400;
          }
          checkStatus(ar.code, ar.message, mode);
          return { data: null, response, error: ar };
        },
      },
      fetchOptions: ri,
    };

    // console.log(cfo);
    return createFetch(cfo)<T>(this.joinPaths(this.prefix, action));
  }

  protected async get<T>(action: string = "", params?: any, mode?: ErrorMessageMode) {
    const ufr = await this.prepare<T>(action + (params instanceof Object ? "?" : "") + new URLSearchParams(params), mode).get();
    return this.read(ufr);
  }

  protected async post<T, P extends Object = {}>(action: string = "", params: P, mode?: ErrorMessageMode): Promise<Ref<T | null>> {
    let ufr: UseFetchReturn<T>;
    const keys = Object.keys(params);
    let blob: Blob | undefined;
    for (const key of keys) {
      const val = Reflect.get(params, key);
      if (val instanceof Blob) {
        blob = val;
        break;
      }
    }
    if (blob) {
      const form = new FormData();
      form.append("model", new Blob([JSON.stringify(params)], { type: "application/json" }));
      form.append("file", blob);
      ufr = await this.prepare<T>(action, mode).post(form);
    } else {
      ufr = await this.prepare<T>(action, mode).post(params, "json");
    }
    return this.read(ufr);
  }

  protected async put<T>(action: string = "", params?: any, mode?: ErrorMessageMode) {
    const ufr = await this.prepare<T>(action, mode).put(params);
    return this.read(ufr);
  }

  protected async delete<T>(action: string = "", params?: any, mode?: ErrorMessageMode) {
    const ufr = await this.prepare<T>(action, mode).delete(params);
    return this.read(ufr);
  }

  private read<T>(ufr: UseFetchReturn<T>) {
    return new Promise<Ref<T | null>>((resolve, reject) => {
      if (ufr.response.value?.ok) {
        resolve(ufr.data);
      } else {
        // reject(ufr.error.value.message);
        console.warn(ufr.error.value.message);
      }
    });
  }

  private checkStatus(status: number, message?: string, mode?: ErrorMessageMode) {
    const { createMessage, createMessageBox } = useMessage();
    let msg = "";

    switch (status) {
      case 400:
        msg = message || "未定义异常。";
        break;
      case 401:
        window.location.href = "/login?redirect=" + window.location.pathname;
        return;
      // msg = message || "用户没有授权（令牌、用户名、密码错误）!";
      // break;
      case 403:
        msg = message || "用户得到授权，但是访问是被禁止的。!";
        break;
      case 404:
        msg = message || "网络请求错误，未找到该资源!";
        break;
      case 405:
        msg = message || "网络请求错误，请求方法未允许!";
        break;
      case 408:
        msg = message || "网络请求超时!";
        break;
      case 500:
        msg = message || "服务器错误，请联系管理员!";
        break;
      case 501:
        msg = message || "系统功能未实现!";
        break;
      case 502:
        msg = "网络错误!";
        break;
      case 503:
        msg = "服务不可用，服务器暂时过载或维护!";
        break;
      case 504:
        msg = "网络超时!";
        break;
      case 505:
        msg = "http版本不支持该请求!";
        break;
      default:
    }

    if (msg) {
      switch (mode) {
        case "message":
          createMessage("error", msg, 10);
          break;
        case "modal":
          createMessageBox("error", msg);
          break;
        case "none":
          break;
        default:
          createMessage("error", msg);
          break;
      }
    }
  }

  private joinPaths(start: string, ...ends: string[]): string {
    let end = ends.shift(); // WARNING: ends is changed.
    if (!end) return start;
    if (start.endsWith("/")) start = start.substring(0, start.length - 1);
    if (end.startsWith("/")) end = end.substring(1);
    if (end.startsWith("?")) this.joinPaths(`${start}${end}`, ...ends);
    return this.joinPaths(`${start}/${end}`, ...ends);
  }
}
